package middleware

import (
	"bytes"
	"encoding/json"
	"fmt"
	"github.com/gin-gonic/gin"
	"go.uber.org/zap"
	"io"
	"mcp-demo/internal/global"
	"mcp-demo/internal/model/constant"
	"mcp-demo/internal/utils/number"
	"slices"
	"time"
)

// LogLayout 日志layout
type LogLayout struct {
	Time         time.Time
	Method       string                 // 请求方法
	RequestId    string                 // 请求id
	Metadata     map[string]interface{} // 存储自定义原数据
	Path         string                 // 访问路径
	Query        string                 // 请求参数
	RequestBody  string                 // 请求体
	ResponseBody any                    // 响应体
	IP           string                 // ip地址
	UserAgent    string                 // 代理
	Cost         int64                  // 花费时间
	Source       string                 // 来源
	StatusCode   int                    // HTTP 状态码
}

type Logger struct {
	// Filter 用户自定义过滤 返回true 则不打印日志
	Filter func(c *gin.Context) bool
	// FilterKeyword 关键字过滤(key)
	FilterKeyword func(layout *LogLayout) bool
	// AuthProcess 鉴权处理
	AuthProcess func(c *gin.Context, layout *LogLayout)
	// 日志处理
	Print func(LogLayout)
	// 日志开始
	PrintStart func(LogLayout)
	// 日志结束
	PrintEnd func(LogLayout)
	// Source 服务唯一标识
	Source string
}

func (l Logger) SetLoggerMiddleware() gin.HandlerFunc {
	return func(c *gin.Context) {
		start := time.Now()
		path := c.Request.URL.Path
		query := c.Request.URL.RawQuery
		// 请求方式
		method := c.Request.Method
		// 获取请求id
		requestId := c.Request.Header.Get(constant.RequestId)
		if requestId == "" {
			// 不存在生成请求id
			requestId = number.GenerateUuidUpper()
			// 设置请求id
			c.Set(constant.RequestId, requestId)
		}

		var body []byte
		body, _ = c.GetRawData()
		// 将原body塞回去
		c.Request.Body = io.NopCloser(bytes.NewBuffer(body))
		// 打印请求
		requestBody := string(body)

		layout := LogLayout{
			Time:        time.Now(),
			Method:      method,
			RequestId:   requestId,
			Path:        path,
			Query:       query,
			RequestBody: requestBody,
			IP:          c.ClientIP(),
			UserAgent:   c.Request.UserAgent(),
			Source:      l.Source,
		}

		// 打印日志 过滤掉不需要打印的接口
		if l.PrintStart != nil && !(l.Filter != nil && l.Filter(c)) {
			l.PrintStart(layout)
		}

		c.Next()

		// 计算耗时
		cost := time.Since(start)
		layout.Cost = cost.Milliseconds()

		// 获取 HTTP 状态码
		layout.StatusCode = c.Writer.Status()

		// 获取时间
		layout.Time = time.Now()

		// 获取响应数据
		res, _ := c.Get(constant.PayloadName)
		if res != nil && res != "" {
			layout.ResponseBody = res
		}

		if l.AuthProcess != nil {
			// 处理鉴权需要的信息
			l.AuthProcess(c, &layout)
		}
		if l.FilterKeyword != nil {
			// 自行判断key/value 脱敏等
			l.FilterKeyword(&layout)
		}

		// 打印日志
		if l.PrintEnd != nil && l.PrintStart != nil && !(l.Filter != nil && l.Filter(c)) {
			l.PrintEnd(layout)
		}

		// 自行处理日志
		if l.Print != nil && l.PrintStart != nil && !(l.Filter != nil && l.Filter(c)) {
			l.Print(layout)
		}
	}
}

func DefaultLogger() gin.HandlerFunc {
	return Logger{
		Print: func(layout LogLayout) {
			// 标准输出,k8s做收集
			v, _ := json.Marshal(layout)
			fmt.Println(string(v))
		},
		Source: global.CONFIG.System.Name,
	}.SetLoggerMiddleware()
}

func FileLogger() gin.HandlerFunc {
	return Logger{
		Filter: func(c *gin.Context) bool {
			path := c.Request.URL.Path
			filters := global.CONFIG.Zap.Filter
			return slices.Contains(filters, path)
		},
		PrintStart: func(layout LogLayout) {
			global.L.Info("请求开始: 接口地址"+layout.Path,
				zap.String("method", layout.Method),
				zap.String("requestId", layout.RequestId),
				zap.String("path", layout.Method),
				zap.String("params", layout.Query),
				zap.String("requestBody", layout.RequestBody),
				zap.String("ip", layout.IP),
				zap.String("userAgent", layout.UserAgent),
				zap.String("ApplicationName", layout.Source))
		},
		PrintEnd: func(layout LogLayout) {
			global.L.Info("请求结束: "+layout.Path,
				zap.Int("statusCode", layout.StatusCode), // 打印状态码
				zap.String("requestId", layout.RequestId),
				zap.String("path", layout.Path),
				zap.String("ip", layout.IP),
				zap.Int64("cost", layout.Cost),
				zap.Any("responseBody", layout.ResponseBody),
				zap.String("ApplicationName", layout.Source))
		},
		Source: global.CONFIG.System.Name,
	}.SetLoggerMiddleware()
}
